import concurrent.futures
import math
import os.path
import sys
from random import choice
import re
import json
import subprocess
import requests
from lxml import etree

'''
DedeUserID：用户的ID
DedeUserID__ckMd5：用户ID的加密值
SESSDATA：用户的会话数据
sid：会话ID
fingerprint：用户的指纹，可以用来标识用户设备信息。
'''


class BilibiliDownload:
    def __init__(self):
        with open('config.json', 'r') as fp:
            self.headers = json.load(fp)
        self.dirname = 'out'
        self.share_link = None
        self.filenames = []

    @staticmethod
    def remove_duplicates(arr):
        return list(dict.fromkeys(arr))

    @staticmethod
    def clean_filename(filename):
        # 将不允许的字符替换为空格
        cleaned_file_name = re.sub(r'[<>:"/\\|?*]', ' ', filename)
        # 去除文件名中的连续空格
        cleaned_file_name = re.sub(r'\s+', ' ', cleaned_file_name)
        # 去除文件名中的前后空格
        cleaned_file_name = cleaned_file_name.strip()
        return cleaned_file_name

    def get_video_audio_url(self):

        self.headers['referer'] = self.share_link
        resp = requests.get(self.share_link, headers=self.headers)
        if not resp.ok:
            Exception(f"get_video_audio_url(): 无法访问{resp.status_code}")
        # html = resp.text
        tree = etree.HTML(resp.text)
        title = tree.xpath('//div[@id="app"]/div[@class="video-container-v1"]/div[1]/div[1]/h1/text()')[0]
        json_str = re.findall('<script>window.__playinfo__=(.*?)</script>', resp.text)[0]
        # print(json_str)
        title = self.clean_filename(title)
        video_info = json.loads(json_str)['data']['dash']
        # 获取视频列表
        video_url = []
        video_url.append(video_info['video'][0]['baseUrl'])
        video_url.extend(video_info['video'][0]['backupUrl'])
        # 获取音频列表
        audio_url = []
        audio_url.append(video_info['audio'][0]['baseUrl'])
        audio_url.extend(video_info['audio'][0]['backupUrl'])

        # segment_base = video_info['video'][0]['segment_base']
        tmp_path = os.path.join(self.dirname, 'tmp')
        video_filename = os.path.join(tmp_path, f'{title}.mp4')
        audio_filename = os.path.join(tmp_path, f'{title}.mp3')

        if not os.path.exists(tmp_path):
            os.makedirs(tmp_path)
        # 多线程下载
        print(video_url)
        print(audio_url)
        # 创建文件
        print(video_filename)
        if not os.path.exists(video_filename):
            open(video_filename, 'wb').close()
        if not os.path.exists(audio_filename):
            open(audio_filename, 'wb').close()

        self.multi_download(video_url, video_filename, audio_url, audio_filename)
        return video_filename, audio_filename

    def get_file_length(self, url):
        # print(self.headers)
        resp = requests.head(url, headers=self.headers)
        if not resp.ok:
            print(f'get_file_length(): {url}无法访问,{resp.status_code}')
            return -1
        total = int(resp.headers['Content-Length'])
        return total

    def multi_download(self, video_url, video_filename, audio_url, audio_filename):
        video_total = self.get_file_length(choice(video_url))
        audio_total = self.get_file_length(choice(audio_url))
        print(f'video size: {video_total}')
        print(f'audio size: {audio_total}')
        if video_total > (1 << 27):
            video_chunk = 1 << 24
        else:
            video_chunk = 1 << 23
        audio_chunk = 1 << 20
        fragment_length = math.ceil(video_total / video_chunk)
        with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
            tasks = []
            for i in range(0, fragment_length):
                start = video_chunk * i
                end = min(video_chunk * (i + 1) - 1, video_total - 1)
                res = executor.submit(self.download, choice(video_url), video_filename, start, end)
                tasks.append(res)

            fragment_length = math.ceil(audio_total / audio_chunk)

            for i in range(0, fragment_length):
                start = audio_chunk * i
                end = min(audio_chunk * (i + 1) - 1, audio_total - 1)
                res = executor.submit(self.download, choice(audio_url), audio_filename, start, end)
                tasks.append(res)
            # res = executor.submit(download, choice(audio_url), audio_filename, 0, audio_total - 1)
            # tasks.append(res)

            # print(f'{end / 1024} kb 已下载')
            # download(video_url, video_filename, start, total)
            completed = 0
            for future in concurrent.futures.as_completed(tasks):
                completed += future.result()
                # future.result()
                completed = min(completed, (video_total + audio_total))
                # subprocess.call('clear')
                # print(f'{video_filename}: 已下载{completed / 1024 / 1024:.3f} MB')

    def download(self, url, filename, start, end):
        self.headers['range'] = f'bytes={start}-{end}'
        res = requests.get(url, headers=self.headers, stream=True)
        if not res.ok:
            print(f"download(): {url}无法访问, {res.status_code}")
            return -1
        # print(start, '-', end, sep='')
        content_range = res.headers['Content-Range']
        # content_length = int(res.headers['Content-Length'])
        content_range = content_range.removeprefix('bytes').strip()
        content_start = int(content_range.split('-')[0])
        if content_start != start:
            print(start, '!= ', content_start)
            return self.download(url, filename, start, end)
        print(res.headers['Content-Range'])

        with open(filename, 'rb+') as fp:
            fp.seek(start)
            # print(fp.tell(),": ",start)
            fp.write(res.content)
        return end - start + 1

    def merge(self, video_file, audio_file, output_file):
        cmd = f'ffmpeg -i "{video_file}" -i "{audio_file}" -c:v copy -c:a aac -strict experimental "{output_file}"'
        # 使用subprocess不弹出控制台
        import subprocess
        process = subprocess.Popen(f"cmd /c {cmd}", shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        # os.system(cmd)
        process.communicate()
        import shutil
        shutil.rmtree(os.path.dirname(video_file))
        self.filenames.append(os.path.basename(output_file))

    def download_share(self):
        self.filenames = [os.path.abspath(self.dirname)]
        video, audio = self.get_video_audio_url()
        self.merge(video, audio, os.path.join(self.dirname, os.path.basename(video)))

if __name__ == '__main__':

    url = None
    if len(sys.argv) > 1:
        url = sys.argv[1]
    print(url)
    # dirname = os.path.dirname(__file__)
    dirname = 'out'
    # url = 'https://www.bilibili.com/video/BV1Q24y1h7i8/'
    # url = 'https://www.bilibili.com/video/BV1kA411S7P5/'
    # video, audio = get_video_audio_url(url)
    # merge(video, audio, os.path.join(dirname, os.path.basename(video)))
